﻿using System;
using System.Collections.Specialized;
using System.Configuration.Provider;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Configuration;
using System.Web.Security;
using ALinq.Mapping;
using System.IO;
using ALinq.Web.Security;

namespace ALinq.Web.Security
{
    public class MembershipProvider : System.Web.Security.MembershipProvider
    {
        public const string ProviderName = "ALinqMembership";

        private string _AppName;
        //private readonly Table<User> userTable;
        //private readonly string path;
        //private MembershipDataContext db;
        private int _SchemaVersionCheck;
        private bool _EnablePasswordRetrieval;
        private bool _EnablePasswordReset;
        private bool _RequiresQuestionAndAnswer;
        private bool _RequiresUniqueEmail;
        private int _MaxInvalidPasswordAttempts;
        private int _PasswordAttemptWindow;
        private int _MinRequiredNonalphanumericCharacters;
        private int _MinRequiredPasswordLength;
        private string _PasswordStrengthRegularExpression;
        private int _CommandTimeout;
        //private MembershipPasswordFormat _PasswordFormat;
        private string mappingFile;
        private string conn;
        string listenerName;

        public override void Initialize(string name, NameValueCollection config)
        {
            base.Initialize(name, config);

            if (config == null)
            {
                throw new ArgumentNullException("config");
            }
            #region MyRegion
            /*
            if (string.IsNullOrEmpty(name))
            {
                name = "ALinqMembershipProvider";
            }
            if (string.IsNullOrEmpty(config["description"]))
            {
                config.Remove("description");
                config.Add("description", SR.GetString("MembershipSqlProvider_description"));
            }
            */

            #endregion
            /*
            if (string.IsNullOrEmpty(config["mappingFile"]))
            {
                throw new ArgumentNullException("mappingFile");
            }*/

            conn = config["connectionString"];
            if (string.IsNullOrEmpty(conn))
            {
                var connName = config["connectionStringName"];
                if (string.IsNullOrEmpty(connName))
                    throw new ArgumentNullException("connectionStringName");

                var setting = WebConfigurationManager.ConnectionStrings[connName];
                if (setting == null)
                    throw new Exception(string.Format("找不到名为“{0}”连接字符串", connName));

                conn = setting.ConnectionString;
            }


            ApplicationName = config["applicationName"];
            if (string.IsNullOrEmpty(ApplicationName))
            {
                ApplicationName = SecUtility.GetDefaultAppName();
            }

            _SchemaVersionCheck = 0;
            _EnablePasswordRetrieval = SecUtility.GetBooleanValue(config, "enablePasswordRetrieval", false);
            _EnablePasswordReset = SecUtility.GetBooleanValue(config, "enablePasswordReset", true);
            _RequiresQuestionAndAnswer = SecUtility.GetBooleanValue(config, "requiresQuestionAndAnswer", true);
            _RequiresUniqueEmail = SecUtility.GetBooleanValue(config, "requiresUniqueEmail", true);
            _MaxInvalidPasswordAttempts = SecUtility.GetIntValue(config, "maxInvalidPasswordAttempts", 5, false, 0);
            _PasswordAttemptWindow = SecUtility.GetIntValue(config, "passwordAttemptWindow", 10, false, 0);
            _MinRequiredPasswordLength = SecUtility.GetIntValue(config, "minRequiredPasswordLength", 7, false, 0x80);
            _MinRequiredNonalphanumericCharacters = SecUtility.GetIntValue(config, "minRequiredNonalphanumericCharacters", 1, true, 0x80);
            _PasswordStrengthRegularExpression = config["passwordStrengthRegularExpression"];

            mappingFile = config["mappingFile"];

            listenerName = config["listenerName"];

            //db = mappingFile == null ? new MembershipDataContext(conn) :
            //                           new MembershipDataContext(conn, XmlMappingSource.FromUrl(mappingFile));

            //if (listenerName != null)
            //{
            //    var listener = Trace.Listeners[listenerName] as TextWriterTraceListener;
            //    if (listener != null)
            //        db.Log = listener.Writer;
            //}
        }

        public User GetUser(string name)
        {
            using (var db = CreateDataContext())
            {
                try
                {
                    var item = db.Users.Where(o => o.UserName == name).SingleOrDefault();
                    item.SetProviderName(Name);
                    return item;
                }
                finally
                {
                    if (db.Log != null)
                        db.Log.Flush();
                }
            }

        }

        public override string ApplicationName
        {
            get;
            set;
        }

        public override bool ChangePassword(string username, string oldPassword, string newPassword)
        {
            using (var db = CreateDataContext())
            {
                var user = db.Users.Where(o => o.UserName == username).SingleOrDefault();
                if (user != null && string.Equals(oldPassword, user.Password, StringComparison.OrdinalIgnoreCase))
                {
                    user.Password = newPassword;
                    db.SubmitChanges();
                    return true;
                }
                return false;
            }
        }

        public override bool ChangePasswordQuestionAndAnswer(string username, string password,
                                                             string newPasswordQuestion, string newPasswordAnswer)
        {
            using(var db = new MembershipDataContext())
            {
                var user = db.Users.Where(o => o.UserName == username & o.Password == password).SingleOrDefault();
                if (user == null)
                    return false;

                user.SetPasswordQuestion(newPasswordQuestion);
                user.PasswordAnswer = newPasswordAnswer;
                db.SubmitChanges();
                return true;
            }

        }

        public override MembershipUser CreateUser(string username, string password,
                                                  string email, string passwordQuestion, string passwordAnswer, bool isApproved,
                                                  object providerUserKey, out MembershipCreateStatus status)
        {

            using (var db = CreateDataContext())
            {
                //var db = CreateDataContext();

                var user = db.Users.Where(o => o.UserName == username).FirstOrDefault();
                if (user != null)
                {
                    status = MembershipCreateStatus.DuplicateUserName;
                    return null;
                }
                user = db.Users.Where(o => o.Email == email).FirstOrDefault();
                if (RequiresUniqueEmail && user != null)
                {
                    status = MembershipCreateStatus.DuplicateEmail;
                    return null;
                }

                user = new User();
                user.SetProviderName(Name);
                user.SetProviderName(this.Name);

                user.ID = db.Users.Max(o => o.ID) + 1;
                user.SetUserName(username);
                user.Password = password;
                user.Email = email;

                //var questionID = db.PasswordQuestions.Where(o => o.Text == passwordQuestion).Select(o => o.ID).Single();
                user.SetPasswordQuestion(passwordQuestion);

                user.PasswordAnswer = passwordAnswer;
                user.IsApproved = isApproved;
               
                user.SetCreationDate(DateTime.Now);
                user.SetIsApproved(isApproved);
                user.SetIsLockedOut(false);
                user.SetLastActivityDate(DateTime.Now);
                user.SetLastLoginDate(DateTime.Now);
                user.SetLastPasswordChangedDate(DateTime.Now);


                try
                {
                    db.Users.InsertOnSubmit(user);
                    db.SubmitChanges();
                    status = MembershipCreateStatus.Success;
                    return user;
                }
                catch (Exception exc)
                {
                    status = MembershipCreateStatus.ProviderError;
                    Utility.LogError(exc);
                    return null;
                }
                finally
                {
                    if (db.Log != null)
                        db.Log.Flush();
                }
            }
        }

        public override bool DeleteUser(string username, bool deleteAllRelatedData)
        {
            using (var db = CreateDataContext())
            {
                var user = db.Users.Where(o => o.UserName == username).SingleOrDefault();
                if (user == null)
                    return false;

                try
                {
                    db.Users.DeleteOnSubmit(user);
                    db.SubmitChanges();
                    return true;
                }
                catch (Exception exc)
                {
                    Utility.LogError(exc);
                    return false;
                }
            }
        }

        public override bool EnablePasswordReset
        {
            get { return this._EnablePasswordReset; }
        }

        public override bool EnablePasswordRetrieval
        {
            get { return this._EnablePasswordRetrieval; }
        }

        public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords)
        {
            using (var db = CreateDataContext())
            {
                totalRecords = db.Users.Where(o => o.Email.Contains(emailToMatch)).Count();

                var skip = (pageIndex*pageIndex);
                var take = pageSize;

                var users = db.Users.Where(o => o.Email.Contains(emailToMatch)).Skip(skip).Take(take).ToArray();

                var result = new MembershipUserCollection();
                foreach (var user in users)
                {
                    user.SetProviderName(Name);
                    result.Add(user);
                }
                return result;
            }
        }

        public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
        {
            using (var db = CreateDataContext())
            {
                totalRecords = db.Users.Where(o => o.UserName.Contains(usernameToMatch)).Count();

                var skip = (pageIndex*pageIndex);
                var take = pageSize;

                var users = db.Users.Where(o => o.UserName.Contains(usernameToMatch)).Skip(skip).Take(take).ToArray();

                var result = new MembershipUserCollection();
                foreach (var user in users)
                {
                    user.SetProviderName(Name);
                    result.Add(user);
                }
                return result;
            }
        }

        public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
        {
            using (var db = CreateDataContext())
            {
                var items = new MembershipUserCollection();
                totalRecords = db.Users.Count();

                var skip = (pageIndex*pageIndex);
                var take = pageSize;

                var num = totalRecords - skip;
                if (num < pageSize)
                    take = num;

                var users = db.Users.Skip(skip).Take(take).ToList();

                foreach (var user in users)
                    items.Add(user);

                return items;
            }
        }

        public override int GetNumberOfUsersOnline()
        {
            return 0;
        }

        public override string GetPassword(string username, string answer)
        {
            using (var db = CreateDataContext())
            {
                string password;
                if (string.IsNullOrEmpty(answer))
                {
                    password = db.Users.Where(o => o.UserName == username)
                        .Select(o => o.Password).SingleOrDefault();
                }
                else
                {
                    password = db.Users.Where(o => o.UserName == username && o.PasswordAnswer == answer)
                        .Select(o => o.Password).SingleOrDefault();
                }
                return password;
            }
        }

        public override MembershipUser GetUser(string username, bool userIsOnline)
        {
            using (var db = CreateDataContext())
            {
                var user = db.Users.Where(o => o.UserName == username).SingleOrDefault();
                if (user != null)
                    user.SetProviderName(Name);

                return user;
            }
        }

        public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
        {
            using (var db = CreateDataContext())
            {
                var user = db.Users.Where(o => o.ID == (int) providerUserKey)
                    .SingleOrDefault();
                if (user != null)
                    user.SetProviderName(Name);
                return user;
            }
         
        }

        public override string GetUserNameByEmail(string email)
        {
            using (var db = CreateDataContext())
            {
                return db.Users.Where(o => o.Email == email)
                    .Select(o => o.UserName).FirstOrDefault();
            }
        }

        public override int MaxInvalidPasswordAttempts
        {
            get { return this._MaxInvalidPasswordAttempts; }
        }

        public override int MinRequiredNonAlphanumericCharacters
        {
            get { return this._MinRequiredNonalphanumericCharacters; }
        }

        public override int MinRequiredPasswordLength
        {
            get { return this._MinRequiredPasswordLength; }
        }

        public override int PasswordAttemptWindow
        {
            get { return this._PasswordAttemptWindow; }
        }

        public override MembershipPasswordFormat PasswordFormat
        {
            get { return MembershipPasswordFormat.Clear; }
        }

        public override string PasswordStrengthRegularExpression
        {
            get { return this._PasswordStrengthRegularExpression; }
        }

        public override bool RequiresQuestionAndAnswer
        {
            get { return this._RequiresQuestionAndAnswer; }
        }

        public override bool RequiresUniqueEmail
        {
            get { return _RequiresUniqueEmail; }
        }

        public override string ResetPassword(string username, string answer)
        {
            using (var db = CreateDataContext())
            {
                var user = db.Users.Where(o => o.UserName == username).SingleOrDefault();
                if (user != null && user.PasswordAnswer == answer)
                {
                    var random = new Random(1000);
                    var password = random.Next(10000000, Int32.MaxValue).ToString();
                    user.Password = password;
                    db.SubmitChanges();
                    return password;
                }
                return null;
            }
        }

        private MembershipDataContext CreateDataContext()
        {
            var db = !string.IsNullOrEmpty(mappingFile)
                     ? new MembershipDataContext(conn, XmlMappingSource.FromUrl(mappingFile))
                     : new MembershipDataContext(conn);

            if (listenerName != null)
            {
                var listener = Trace.Listeners[listenerName] as TextWriterTraceListener;
                if (listener != null)
                    db.Log = listener.Writer;
            }
            return db;
        }

        public override bool UnlockUser(string userName)
        {
            using (var db = CreateDataContext())
            {
                var user = db.Users.Where(o => o.UserName == userName).SingleOrDefault();
                if (user == null)
                    return false;
                user.IsLocked = false;
                try
                {
                    db.SubmitChanges();
                    return true;
                }
                catch (Exception exc)
                {
                    Utility.LogError(exc);
                    return false;
                }
            }
        }

        public override void UpdateUser(MembershipUser user)
        {
            if (user is User)
            {
                using (var db = CreateDataContext())
                {
                    var a = db.Users.GetOriginalEntityState((User) user);
                    if (a.ID == 0)
                        db.Users.Attach((User) user, true);

                    db.SubmitChanges();
                }
            }
            else
            {
                throw new NotImplementedException();
            }
        }

        public override bool ValidateUser(string username, string password)
        {
            using (var db = CreateDataContext())
            {
                var user = db.Users.Select(o => new {o.Password, o.IsApproved, o.UserName})
                    .SingleOrDefault(o => o.UserName == username);

                if (user == null)
                    return false;

                return user.Password == password;
            }
        }
    }
}